from khronos.utils.parsing.lexer import Token, Lexer
from khronos.utils import indent

class ID(Token):
    regex = r"[a-zA-Z_][a-zA-Z_0-9]*"
    
class BNF(object):
    """Recursive-descent parser for BNF grammar rules."""
    def __init__(self):
        self.lexer = Lexer([ID], literals=",|[]{}()\"", ignore=" \t")
        
    def parse(self, rule):
        self.lexer.init(rule)
        seq = self.sequence()
        if not self.lexer.finished():
            raise Exception("unparsed input detected at position %d" % (self.lexer.position,))
        return seq
        
    def accept(self, token_type):
        token = self.lexer.get_next()
        if token is None or token.type != token_type:
            self.lexer.put_back(token)
            return False
        return True
        
    def expect(self, token_type):
        if not self.accept(token_type):
            raise SyntaxError("expecting token '%s'" % (token_type,))
            
    def sequence(self):
        seq = Sequence()
        seq.append(self.element())
        while self.accept(","):
            seq.append(self.element())
        if len(seq) == 1:
            return seq[0]
        return seq
        
    def element(self):
        result = None
        token = self.lexer.get_next()
        if token.type == "ID":
            result = token
        elif token.type == "(":
            result = self.sequence()
            self.expect(")")
        elif token.type == "[":
            result = Optional(self.sequence())
            self.expect("]")
        elif token.value == "{":
            result = Repeat(self.sequence())
            self.expect("}")
        else:
            raise SyntaxError("unexpected token %s" % (token,))
        if self.accept("|"):
            result = Choice([result, self.element()])
        return result
        
class Choice(list):
    def __str__(self):
        lines = [self.__class__.__name__ + "<",]
        lines.extend(indent(str(elem)) for elem in self)
        lines.append(">")
        return "\n".join(lines)
        
class Sequence(list):
    def __str__(self):
        lines = [self.__class__.__name__ + "<",]
        lines.extend(indent(str(elem)) for elem in self)
        lines.append(">")
        return "\n".join(lines)
        
class Optional(object):
    def __init__(self, elem):
        self.element = elem
        
    def __str__(self):
        return "%s<\n%s\n>" % (self.__class__.__name__, indent(str(self.element)))
        
class Repeat(object):
    def __init__(self, elem):
        self.element = elem
        
    def __str__(self):
        return "%s<\n%s\n>" % (self.__class__.__name__, indent(str(self.element)))
        